package munci.core;

import java.lang.reflect.Type;
import java.util.Arrays;

public class Partial<R> implements Word {

	private final Object value;
	private final Word word;
	
	private Partial(Word word, Object value) {
		this.word = word;
		this.value = value;
	}
	
	public boolean hasAttribute(Object attribute) {
		return word.hasAttribute(attribute);
	}

	@SuppressWarnings("unchecked") public R invoke(Object... args) {
		Object[] values = new Object[args.length + 1];
		values[0] = value;
		System.arraycopy(args, 0, values, 1, args.length);
		return (R) word.invoke(values);
	}

	public Class<?>[] parameterTypes() {
		Class<?>[] types = word.parameterTypes();
		return Arrays.copyOfRange(types, 1, types.length);
	}

	@SuppressWarnings("unchecked") public Class<R> returnType() {
		return (Class<R>) word.returnType();
	}
	
	public Type genericReturnType() {
		return word.genericReturnType();
	}
	
	@Override public String toString() {
		return value + " -> " + word;
	}
	
	private static abstract class V0 extends Partial<Object> implements Word.V0 {
		V0(Word word, Object value) {super(word, value);}
	}

	private static abstract class V1<P1> extends Partial<Object> implements Word.V1<P1> {
		V1(Word word, Object value) {super(word, value);}
	}

	private static abstract class V2<P1, P2> extends Partial<Object> implements Word.V2<P1, P2> {
		V2(Word word, Object value) {super(word, value);}
	}

	private static abstract class V3<P1, P2, P3> extends Partial<Object> implements Word.V3<P1, P2, P3> {
		V3(Word word, Object value) {super(word, value);}
	}
	
	private static abstract class R0<R> extends Partial<R> implements Word.R0<R> {
		R0(Word word, Object value) {super(word, value);}
	}
	
	private static abstract class R1<R, P1> extends Partial<R> implements Word.R1<R, P1> {
		R1(Word word, Object value) {super(word, value);}
	}
	
	private static abstract class R2<R, P1, P2> extends Partial<R> implements Word.R2<R, P1, P2> {
		R2(Word word, Object value) {super(word, value);}
	}
	
	private static abstract class R3<R, P1, P2, P3> extends Partial<R> implements Word.R3<R, P1, P2, P3> {
		R3(Word word, Object value) {super(word, value);}
	}
	
	public static <P1> Word.V0 apply(final Word.V1<P1> word, final P1 p1) {
		return new V0(word, p1) {
			public void call() {
				word.call(p1);
			}
		};
	}

	public static <P1, P2> Word.V1<P2> apply(final Word.V2<P1, P2> word, final P1 p1) {
		return new V1<P2>(word, p1) {
			public void call(P2 p2) {
				word.call(p1, p2);
			}
		};
	}

	public static <P1, P2, P3> Word.V2<P2, P3> apply(final Word.V3<P1, P2, P3> word, final P1 p1) {
		return new V2<P2, P3>(word, p1) {
			public void call(P2 p2, P3 p3) {
				word.call(p1, p2, p3);
			}
		};
	}

	public static <P1, P2, P3, P4> Word.V3<P2, P3, P4> apply(final Word.V4<P1, P2, P3, P4> word, final P1 p1) {
		return new V3<P2, P3, P4>(word, p1) {
			public void call(P2 p2, P3 p3, P4 p4) {
				word.call(p1, p2, p3, p4);
			}
		};
	}
	
	public static <R, P1> Word.R0<R> apply(final Word.R1<R, P1> word, final P1 p1) {
		return new R0<R>(word, p1) {
			public R call() {
				return word.call(p1);
			}
		};
	}
	
	public static <R, P1, P2> Word.R1<R, P2> apply(final Word.R2<R, P1, P2> word, final P1 p1) {
		return new R1<R, P2>(word, p1) {
			public R call(P2 p2) {
				return word.call(p1, p2);
			}
		};
	}
	
	public static <R, P1, P2, P3> Word.R2<R, P2, P3> apply(final Word.R3<R, P1, P2, P3> word, final P1 p1) {
		return new R2<R, P2, P3>(word, p1) {
			public R call(P2 p2, P3 p3) {
				return word.call(p1, p2, p3);
			}
		};
	}
	
	public static <R, P1, P2, P3, P4> Word.R3<R, P2, P3, P4> apply(final Word.R4<R, P1, P2, P3, P4> word, final P1 p1) {
		return new R3<R, P2, P3, P4>(word, p1) {
			public R call(P2 p2, P3 p3, P4 p4) {
				return word.call(p1, p2, p3, p4);
			}
		};
	}
}
